# name = input('please enter your name: ')
# print('hello,', name)

# a = 100
# if a >= 0:
#     print(a)
# else:
#     print(-a)


# print(r'''hello,\n
# world''')


# age=20
# if age >= 18:
#     print('adult')
# else:
#     print('teenager')

# a = 123 # a是整数
# print(a)
# a = 'ABC' # a变为字符串
# print(a)


n = 123
f = 456.789
s1 = 'Hello, world'
s2 = 'Hello, \'Adam\''
s3 = r'Hello, "Bart"'
s4 = r'''Hello,
Lisa!'''
print(n,f,s1,s2,s3,s4)
print("中文测试")

#format，字符串格式化
x1=72
x2=85
r=(x2-x1)/x1*100
print('Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', r))

#Python内置的一种数据类型是列表：list。list是一种有序的集合，可以随时添加和删除其中的元素。
classmates = ['Michael', 'Bob', 'Tracy']
#可以往list中追加元素到末尾：
classmates.append('Admin')
#也可以把元素插入到指定的位置，比如索引号为1的位置：
classmates.insert(1, 'Jack')
#要删除list末尾的元素，用pop()方法：
classmates.pop()
#要删除指定位置的元素，用pop(i)方法，其中i是索引位置：
classmates.pop(1)
#要把某个元素替换成别的元素，可以直接赋值给对应的索引位置：
classmates[1] = 'Sarah'
print(classmates)


# 占位符	替换内容
# %d	整数
# %f	浮点数
# %s	字符串
# %x	十六进制整数

# format()
# 另一种格式化字符串的方法是使用字符串的format()方法，它会用传入的参数依次替换字符串内的占位符{0}、{1}……，不过这种方式写起来比%要麻烦得多：
print('长度: %s'%(len(classmates)))
print(classmates[-3])


# tuple另一种有序列表叫元组：tuple。tuple和list非常类似，但是tuple一旦初始化就不能修改，比如同样是列出同学的名字
classmates = ('Michael', 'Bob', 'Tracy')
print(classmates)  

#如果要定义一个空的tuple，可以写成()：
t = ()
print(t)
#但是，要定义一个只有1个元素的tuple，如果你这么定义：
t = (1)
print(t)
#定义的不是tuple，是1这个数！这是因为括号()既可以表示tuple，又可以表示数学公式中的小括号，这就产生了歧义，因此，Python规定，这种情况下，按小括号进行计算，计算结果自然是1。
#所以，只有1个元素的tuple定义时必须加一个逗号,，来消除歧义：
t = (1,)
print(t)

t = ('a', 'b', ['A', 'B'])
t[2][0] = 'X'
t[2][1] = 'Y'
print(t)

L = [
    ['Apple', 'Google', 'Microsoft'],
    ['Java', 'Python', 'Ruby', 'PHP'],
    ['Adam', 'Bart', 'Lisa']
]
# 打印Apple:
print(L[0][0])
# 打印Python:
print(L[1][1])
# 打印Lisa:
print(L[2][2])

#条件判断
age = 3
if age >= 18:
    print('your age is', age)
    print('adult')
    print('age >= 18 third row')
else:
    print('your age is', age)
    print('teenager')
    print('else third row')

# s = int(input('birth: '))
# if s < 2000:
#     print('00前')
# else:
#     print('00后')


#模式匹配
score = 'B'

match score:
    case 'A':
        print('score is A.')
    case 'B':
        print('score is B.')
    case 'C':
        print('score is C.')
    case _: # _表示匹配到其他任何情况
        print('score is ???.')   

args = ['gcc', 'hello.c', 'world.c']
# args = ['clean']
# args = ['gcc']

match args:
    # 如果仅出现gcc，报错:
    case ['gcc']:
        print('gcc: missing source file(s).')
    # 出现gcc，且至少指定了一个文件:
    case ['gcc', file1, *files]:
        print('gcc compile: ' + file1 + ', ' + ', '.join(files))
    # 仅出现clean:
    case ['clean']:
        print('clean')
    case _:
        print('invalid command.')
#Python的循环有两种，一种是for...in循环，依次把list或tuple中的每个元素迭代出来，看例子：
names = ['Michael', 'Bob', 'Tracy']
for name in names:
    print(name)
sum = 0
for x in range(101):
    sum = sum + x
print(sum)

#第二种循环是while循环，只要条件满足，就不断循环，条件不满足时退出循环。比如我们要计算100以内所有奇数之和，可以用while循环实现：
sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)

#在循环中，break语句可以提前退出循环；
#continue 在循环过程中，也可以通过continue语句，跳过当前的这次循环，直接开始下一次循环。

#Python内置了字典：dict的支持，dict全称dictionary，在其他语言中也称为map，使用键-值（key-value）存储，具有极快的查找速度。
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
print(d['Michael'])
d['Jack'] = 90
print(d['Jack'])
#要避免key不存在的错误，有两种办法，
#一是通过in判断key是否存在：
print('Thomas' in d)
#二是通过dict提供的get()方法，如果key不存在，可以返回None，或者自己指定的value：
print(d.get('Thomas'))
print(d.get('Thomas',-1))
#要删除一个key，用pop(key)方法，对应的value也会从dict中删除：
d.pop('Bob')
print(d)

# 请务必注意，dict内部存放的顺序和key放入的顺序是没有关系的。

# 和list比较，dict有以下几个特点：

# 查找和插入的速度极快，不会随着key的增加而变慢；
# 需要占用大量的内存，内存浪费多。
# 而list相反：

# 查找和插入的时间随着元素的增加而增加；
# 占用空间小，浪费内存很少。
# 所以，dict是用空间来换取时间的一种方法。

# dict可以用在需要高速查找的很多地方，在Python代码中几乎无处不在，正确使用dict非常重要，需要牢记的第一条就是dict的key必须是不可变对象。
# key = [1, 2, 3]
# d[key] = 'a list'

#set
#set和dict类似，也是一组key的集合，但不存储value。由于key不能重复，所以，在set中，没有重复的key。

#要创建一个set，需要提供一个list作为输入集合：
s=set([1,2,3])
print(s)
s = set([1, 1, 2, 2, 3, 3])
print(s)
s.add(4)
s.remove(4)
print(s)
#set可以看成数学意义上的无序和无重复元素的集合，因此，两个set可以做数学意义上的交集、并集等操作：

s1 = set([1, 2, 3])
s2 = set([2, 3, 4])
print(s1 & s2)
print('s1和s2的交集:',s1 & s2)
print('s1和s2的并集:',s1 | s2)

a = ['c', 'b', 'a']
a.sort()
print(a)

#而对于不可变对象，比如str，对str进行操作呢：
_str = 'abc'
print(_str.replace('a', 'A'))
#求绝对值的函数abs

print(abs(100))

print(abs(-20))

print(abs(12.34))

#求最大值
print(max(1, 2))
print(max(2, 3, 1, -5))
#数据类型转换
print(int('123'))
print(str(1.23))
print(str(100))
print(bool(1))
print(bool(''))
print(bool(0))
#函数名其实就是指向一个函数对象的引用，完全可以把函数名赋给一个变量，相当于给这个函数起了一个“别名”：
a = abs # 变量a指向abs函数
print(a(-1)) # 所以也可以通过a调用abs函数
print(abs(-600))
#利用Python内置的hex()函数把一个整数转换成十六进制表示的字符串：
n1 = 255
n2 = 1000
print(hex(n1),hex(n2))

#定义函数 
# 如果你已经把my_abs()的函数定义保存为abstest.py文件了，那么，可以在该文件的当前目录下启动Python解释器，用from abstest import my_abs来导入my_abs()函数，注意abstest是文件名（不含.py扩展名）：

# def my_abs(x):
#     if x >= 0:
#         return x
#     else:
#         return -x
from abstest import my_abs
print(my_abs(-99))
def my_func(x):
    #return  'speak: %s'%(my_abs('66'))
    return  'speak: %s'%(my_abs(66))
print(my_func(66))
# 空函数
# 如果想定义一个什么事也不做的空函数，可以用pass语句：
def nop():
    pass
print(nop())


# 返回多个值
# 函数可以返回多个值吗？答案是肯定的。

# 比如在游戏中经常需要从一个点移动到另一个点，给出坐标、位移和角度，就可以计算出新的坐标：
import math

def move(x, y, step, angle=0):
    aa=math.cos(angle)
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny
print(move(1,2,3))

#但其实这只是一种假象，Python函数返回的仍然是单一值：
# 原来返回值是一个tuple！但是，在语法上，返回一个tuple可以省略括号，而多个变量可以同时接收一个tuple，按位置赋给对应的值，所以，Python的函数返回多值其实就是返回一个tuple，但写起来更方便。
r = move(100, 100, 60, math.pi / 6)
print(r)
#计算平方根可以调用math.sqrt()函数：
print(math.sqrt(4))


#请定义一个函数quadratic(a, b, c)，接收3个参数，返回一元二次方程
def quadratic(a, b, c):
  # 计算判别式
    discriminant = b ** 2 - 4 * a * c

    # 如果判别式大于0，则有两个实数根

    if discriminant > 0:

        root1 = (-b + math.sqrt(discriminant)) / (2 * a)

        root2 = (-b - math.sqrt(discriminant)) / (2 * a)

        return root1, root2

    # 如果判别式等于0，则有一个实数根

    elif discriminant == 0:

        root1 = -b / (2 * a)

        return root1

    # 如果判别式小于0，则无实数根

    else:

        return "无实数根"

# # 输入参数

# a = float(input('请输入方程的a值:'))

# b = float(input('请输入方程的b值:'))

# c = float(input('请输入方程的c值:'))

# # 调用函数，求解方程

# roots = quadratic(a, b, c)

# # 输出结果

# print("方程的根为：", quadratic(a, b, c))
print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))

if quadratic(2, 3, 1) != (-0.5, -1.0):
    print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
    print('测试失败')
else:
    print('测试成功')

#计算任意次方的函数：
# 一是必选参数在前，默认参数在后，否则Python的解释器会报错（思考一下为什么默认参数不能放在必选参数前面）；

# 二是如何设置默认参数。

# 当函数有多个参数时，把变化大的参数放前面，变化小的参数放后面。变化小的参数就可以作为默认参数。
def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s
print(power(-2,4))
#方法 默认参数(n为默认参数)
print(power(3))

def enroll(name, gender):
    print('name:', name)
    print('gender:', gender)

# 默认参数很有用，但使用不当，也会掉坑里。默认参数有个最大的坑，演示如下：

# 先定义一个函数，传入一个list，添加一个END再返回：

def add_end(L=[]):
    L.append('END')
    return L
# 当你正常调用时，结果似乎不错：

add_end([1, 2, 3])
[1, 2, 3, 'END']
add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']
# 当你使用默认参数调用时，一开始结果也是对的：

add_end()

# 但是，再次调用add_end()时，结果就不对了：

add_end()

add_end()

print(add_end())

#  定义默认参数要牢记一点：默认参数必须指向不变对象！
# 要修改上面的例子，我们可以用None这个不变对象来实现：

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L
# 现在，无论调用多少次，都不会有问题：

add_end()

add_end()

print(add_end())

# 可变参数
# 在Python函数中，还可以定义可变参数。顾名思义，可变参数就是传入的参数个数是可变的，可以是1个、2个到任意个，还可以是0个。

# 我们以数学题为例子，给定一组数字a，b，c……，请计算a2 + b2 + c2 + ……。

# 要定义出这个函数，我们必须确定输入的参数。由于参数个数不确定，我们首先想到可以把a，b，c……作为一个list或tuple传进来，这样，函数可以定义如下：

def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
# 但是调用的时候，需要先组装出一个list或tuple：
print(calc([1, 2, 3])) # list
print(calc((1, 3, 5, 7))) # tuple

# 我们把函数的参数改为可变参数：

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

# 如果利用可变参数，调用函数的方式可以简化成这样：
print(calc(1, 2, 3))
print(calc(1, 3, 5, 7))

# 如果已经有一个list或者tuple，要调用一个可变参数怎么办？可以这样做：

nums = [1, 2, 3]
calc(nums[0], nums[1], nums[2])
# 这种写法当然是可行的，问题是太繁琐，所以Python允许你在list或tuple前面加一个*号，把list或tuple的元素变成可变参数传进去：

nums = [1, 2, 3]
print(calc(*nums))

# 关键字参数
# 可变参数允许你传入0个或任意个参数，这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装为一个dict。请看示例：

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
person("小明","29")
person('Bob', 35, city='Beijing')
person('Adam', 45, gender='M', job='Engineer')
# 和可变参数类似，也可以先组装出一个dict，然后，把该dict转换为关键字参数传进去：
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, city=extra['city'], job=extra['job'])
# 由于命名关键字参数city具有默认值，调用时，可不传入city参数：
def person(name, age, *, city='Beijing', job):
    print(name, age, city, job)
person('Jack', 24, job='Engineer')

# 如果函数定义中已经有了一个可变参数，后面跟着的命名关键字参数就不再需要一个特殊分隔符*了：

def person(name, age, *args, city, job):
    print(name, age, args, city, job)

#命名关键字参数必须传入参数名，这和位置参数不同。如果没有传入参数名，调用将报错：
#报错调用代码
# person('Jack', 24, 'Beijing', 'Engineer')
my_tuple = ("wang","yuan","wai")
#正确调用
person('Jack', 24,*my_tuple,city='Beijing',job="工作")   
person('Jack', 24,'哈哈哈',city='Beijing',job="工作")   
person(*my_tuple,city='Beijing',job="工作")   
def person(name, age, city, job):
    # 缺少 *，city和job被视为位置参数
    pass

# 参数组合
# 在Python中定义函数，可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数，这5种参数都可以组合使用。但是请注意，参数定义的顺序必须是：必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
# 比如定义一个函数，包含上述若干种参数：
# a,b是必选参数 c是默认参数；*args是可变参数；**kw是关键字参数
def f1(a,b,c=0,*args,**kw):
     print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
# 在函数调用的时候，Python解释器自动按照参数位置和参数名把对应的参数传进去。
f1(1, 2)
f1(1, 2, c=3)
f1(1, 2, 3, 'a', 'b')
f1(1, 2, 3, 'a', 'b', x=99)
f2(1, 2, d=99, ext=None)

# 最神奇的是通过一个tuple和dict，你也可以调用上述函数：

args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw)

args = (1, 2, 3)
kw = {'d': 88, 'x': '#'}
f2(*args, **kw)

#以下函数允许计算两个数的乘积，请稍加改造，变成可接收一个或多个数并计算乘积：
# def mul(x, y):
#     return x * y
def mul(x, y=1, *val):
    ret = 1
    for i in val:
        ret = ret * i
    return x * y * ret
vals=(1,2,3,4,5)

print(mul(*vals))
# 递归函数
# 在函数内部，可以调用其他函数。如果一个函数在内部调用自身本身，这个函数就是递归函数。
def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

print(fact(3))
# 栈就会减一层栈帧。由于栈的大小不是无限的，所以，递归调用的次数过多，会导致栈溢出。
# print(fact(1000))

# 解决递归调用栈溢出的方法是通过尾递归优化，事实上尾递归和循环的效果是一样的，所以，把循环看成是一种特殊的尾递归函数也是可以的。
# 尾递归是指，在函数返回的时候，调用自身本身，并且，return语句不能包含表达式。这样，编译器或者解释器就可以把尾递归做优化，使递归本身无论调用多少次，都只占用一个栈帧，不会出现栈溢出的情况。
def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

print(fact(100))

def f(n):
    if n==0:
        return 0
    else:
        return 2*f(n-1)+1
# x=int(input("请输入片的个数："))
print("需要移动",f(2),"次")

def hanoi(n, a, b, c): 
   if n == 1:
       print(f'将 {a} 移动到 {c}')
   else:
        #将上一层的所有从a->b
       hanoi(n - 1, a, c, b) 
      # 那么a上就只有最底下的一块了，将这个最大的块移动到c上
       hanoi(1, a, b, c) 
      # 再将之前的n-1块移到c上。
       hanoi(n - 1, b, a, c)
print(hanoi(2,'A','B','C'))

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
print([L[0], L[1], L[2]])

r = []
n = 3
for i in range(n):
    r.append(L[i])
print(r)

# 对这种经常取指定索引范围的操作，用循环十分繁琐，因此，Python提供了切片（Slice）操作符，能大大简化这种操作。

# 对应上面的问题，取前3个元素，用一行代码就可以完成切片：
print(L[0:5])
# 如果第一个索引是0，还可以省略：
print(L[:3])

#也可以从索引1开始，取出2个元素出来：
print(L[1:3])

# 类似的，既然Python支持L[-1]取倒数第一个元素，那么它同样支持倒数切片，试试：
print(L[-2:])

print(L[-2:-1])
# 切片操作十分有用。我们先创建一个0-99的数列：
L = list(range(100))
print(L)
# 可以通过切片轻松取出某一段数列。比如前10个数：
print(L[:10])
# 后10个数：
print(L[-10:])
# 前11-20个数：
print(L[10:20])
# 前10个数，每两个取一个
print(L[:10:2])
# 所有数，每5个取一个：
print(L[::5])
# 甚至什么都不写，只写[:]就可以原样复制一个list：
print(L[:])
# tuple也是一种list，唯一区别是tuple不可变。因此，tuple也可以用切片操作，只是操作的结果仍是tuple：
print((0, 1, 2, 3, 4, 5)[:3])
# 字符串'xxx'也可以看成是一种list，每个元素就是一个字符。因此，字符串也可以用切片操作，只是操作结果仍是字符串：
print('ABCDEFG'[:3])
print('ABCDEFG'[::2])
def trim(s):
    if not s:
        return s
    if s[0] == '' or s[0] == ' ':
        s = trim(s[1:])
    elif s[-1] == '' or  s[-1]==' ':
        s = trim(s[:-1])
    return s   
if trim('hello  ') != 'hello':
    print('测试失败!')
elif trim('  hello') != 'hello':
    print('测试失败!')
elif trim('  hello  ') != 'hello':
    print('测试失败!')
elif trim('  hello  world  ') != 'hello  world':
    print('测试失败!')
elif trim('') != '':
    print('测试失败!')
elif trim('    ') != '':
    print('测试失败!')
else:
    print('测试成功!')

# 迭代
# 可以看出，Python的for循环抽象程度要高于C的for循环，因为Python的for循环不仅可以用在list或tuple上，还可以作用在其他可迭代对象上。
# list这种数据类型虽然有下标，但很多其他数据类型是没有下标的，但是，只要是可迭代对象，无论有无下标，都可以迭代，比如dict就可以迭代：  
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
    print(key)

# 因为dict的存储不是按照list的方式顺序排列，所以，迭代出的结果顺序很可能不一样。
# 默认情况下，dict迭代的是key。如果要迭代value，可以用for value in d.values()，如果要同时迭代key和value，可以用for k, v in d.items()。
for value in d.values():
    print(value)   

for key,value in d.items():
    print('键：%s；值：%s'%(key,value)) 

# 由于字符串也是可迭代对象，因此，也可以作用于for循环：
for ch in 'ABC':
    print(ch)
from collections.abc import Iterable
print(isinstance('abc', Iterable)) # str是否可迭代
print(isinstance([1,2,3], Iterable))
print(isinstance(123, Iterable)) # 整数是否可迭代
# 最后一个小问题，如果要对list实现类似Java那样的下标循环怎么办？Python内置的enumerate函数可以把一个list变成索引-元素对，这样就可以在for循环中同时迭代索引和元素本身：
for i, value in enumerate(['A', 'B', 'C']):
    print(i, value)

# 上面的for循环里，同时引用了两个变量，在Python里是很常见的，比如下面的代码：
for x, y in [(1, 1), (2, 4), (3, 9)]:
     print(x, y)
# 请使用迭代查找一个list中最小和最大值，并返回一个tuple：
def findMinAndMax(L):
    if L == []:
        return (None, None)    
    L.sort()    
    return L[0],L[-1]
ilist=[7,9,4,6,1]
print(findMinAndMax(ilist))

# 列表生成式
# 举个例子，要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11))：
print(list(range(1, 11)))
# 但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做？方法一是循环：
L = []
for x in range(1, 11):
    L.append(x * x)
print(L)

# 但是循环太繁琐，而列表生成式则可以用一行语句代替循环生成上面的list：
flist=[x * x for x in range(1, 11)]
print(flist)
# 写列表生成式时，把要生成的元素x * x放到前面，后面跟for循环，就可以把list创建出来，十分有用，多写几次，很快就可以熟悉这种语法。

# for循环后面还可以加上if判断，这样我们就可以筛选出仅偶数的平方： 
elist=[x * x for x in range(1, 11) if x % 2 == 0]
print(elist)

ffList=[m + n for m in 'ABC' for n in 'XYZ']
print(ffList)
# 三层和三层以上的循环就很少用到了。

# 运用列表生成式，可以写出非常简洁的代码。例如，列出当前目录下的所有文件和目录名，可以通过一行代码实现：
import os # 导入os模块，模块的概念后面讲到
dirlist=[d for d in os.listdir('.')] # os.listdir可以列出文件和目录
print(dirlist)

# for循环其实可以同时使用两个甚至多个变量，比如dict的items()可以同时迭代key和value：
d = {'x': 'A', 'y': 'B', 'z': 'C' }
for k, v in d.items():
     print(k, '=', v)
# 因此，列表生成式也可以使用两个变量来生成list：
d = {'x': 'A', 'y': 'B', 'z': 'C' }
dnewlist= [k + '=' + v for k, v in d.items()]
print(dnewlist)

# 最后把一个list中所有的字符串变成小写：
L = ['Hello', 'World', 'IBM', 'Apple']
lowerletter=[s.lower() for s in L]
print(lowerletter)

# if ... else
# 使用列表生成式的时候，有些童鞋经常搞不清楚if...else的用法。

# 例如，以下代码正常输出偶数：
# [x for x in range(1, 11) if x % 2 == 0]

# 但是，我们不能在最后的if加上else：
# [x for x in range(1, 11) if x % 2 == 0 else 0]
# SyntaxError: invalid syntax

# 这是因为跟在for后面的if是一个筛选条件，不能带else，否则如何筛选？

# 另一些童鞋发现把if写在for前面必须加else，否则报错：

# [x if x % 2 == 0 for x in range(1, 11)]

print([x if x % 2 == 0 else -x for x in range(1, 11)])

# 如果list中既包含字符串，又包含整数，由于非字符串类型没有lower()方法，所以列表生成式会报错： 
# 使用内建的isinstance函数可以判断一个变量是不是字符串：
L1 = ['Hello', 'World', 18, 'Apple', None]
L2 = [s.lower() for s in L1 if isinstance(s,str)]
print(L2)
if L2 == ['hello', 'world', 'apple']:
    print('测试通过!')
else:
    print('测试失败!')

# 生成器

L = [x * x for x in range(10)]
print(L);
# 通过列表生成式，我们可以直接创建一个列表。但是，受到内存限制，列表容量肯定是有限的。而且，创建一个包含100万个元素的列表，不仅占用很大的存储空间，如果我们仅仅需要访问前面几个元素，那后面绝大多数元素占用的空间都白白浪费了。
# 所以，如果列表元素可以按照某种算法推算出来，那我们是否可以在循环的过程中不断推算出后续的元素呢？这样就不必创建完整的list，从而节省大量的空间。在Python中，这种一边循环一边计算的机制，称为生成器：generator。
# 要创建一个generator，有很多种方法。第一种方法很简单，只要把一个列表生成式的[]改成()，就创建了一个generator：
g = (x * x for x in range(10))
print(g);

# 创建L和g的区别仅在于最外层的[]和()，L是一个list，而g是一个generator。

# 我们可以直接打印出list的每一个元素，但我们怎么打印出generator的每一个元素呢？

# 如果要一个一个打印出来，可以通过next()函数获得generator的下一个返回值：
print(next(g));
print(next(g));
print(next(g));
print(next(g));
print(next(g));
print(next(g));
# 我们讲过，generator保存的是算法，每次调用next(g)，就计算出g的下一个元素的值，直到计算到最后一个元素，没有更多的元素时，抛出StopIteration的错误。

# 当然，上面这种不断调用next(g)实在是太变态了，正确的方法是使用for循环，因为generator也是可迭代对象：
for n in g:
    print(n);

# 所以，我们创建了一个generator后，基本上永远不会调用next()，而是通过for循环来迭代它，并且不需要关心StopIteration的错误。

# generator非常强大。如果推算的算法比较复杂，用类似列表生成式的for循环无法实现的时候，还可以用函数来实现。

# 比如，著名的斐波拉契数列（Fibonacci），除第一个和第二个数外，任意一个数都可由前两个数相加得到：

# 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

# 斐波拉契数列用列表生成式写不出来，但是，用函数把它打印出来却很容易：

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

# 注意，赋值语句：

# a, b = b, a + b
# 相当于：

# t = (b, a + b) # t是一个tuple
# a = t[0]
# b = t[1]
print(fib(6))

# 仔细观察，可以看出，fib函数实际上是定义了斐波拉契数列的推算规则，可以从第一个元素开始，推算出后续任意的元素，这种逻辑其实非常类似generator。

# 也就是说，上面的函数和generator仅一步之遥。要把fib函数变成generator函数，只需要把print(b)改为yield b就可以了：

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
print(fib(6));

# 这里，最难理解的就是generator函数和普通函数的执行流程不一样。普通函数是顺序执行，遇到return语句或者最后一行函数语句就返回。而变成generator的函数，在每次调用next()的时候执行，遇到yield语句返回，再次执行时从上次返回的yield语句处继续执行。

# 举个简单的例子，定义一个generator函数，依次返回数字1，3，5：

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)
o=odd();
print(next(o));


# 杨辉三角一般解法?
def triangle(n):
    l=[] #用来存储所有行的返回列表
    for i in range(n):
        if i==0:
           l.append([1]) #第一行
        elif i==1:
           l.append([1,1]) #第二行
        else:
            y=[]           #存储一行，每次清空
            for j in range(i+1):
                if j==0 or j==i:
                    y.append(1) #首行和尾行为1
                else:
                    y.append(l[i-1][j]+l[i-1][j-1]) #规律：当前值等于：上一行当前位置(索引)值加上上一行当前位置前一位的值
            l.append(y)
    return l;     
op=triangle(4)
for i in range(len(op)):
    print(op[i])

# 迭代器
# 我们已经知道，可以直接作用于for循环的数据类型有以下几种：

# 一类是集合数据类型，如list、tuple、dict、set、str等；

# 一类是generator，包括生成器和带yield的generator function。

# 这些可以直接作用于for循环的对象统称为可迭代对象：Iterable。

# 可以使用isinstance()判断一个对象是否是Iterable对象：
from collections.abc import Iterable   
print(isinstance([], Iterable))       

#  生成器都是Iterator对象，但list、dict、str虽然是Iterable，却不是Iterator。

# 把list、dict、str等Iterable变成Iterator可以使用iter()函数：
from collections.abc import Iterator
print(isinstance(iter('abc'), Iterator))

# Python的for循环本质上就是通过不断调用next()函数实现的，例如：

for x in [1, 2, 3, 4, 5]:
    pass
# 实际上完全等价于：

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

#map/reduce

# Python内建了map()和reduce()函数。

# 如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clusters”，你就能大概明白map/reduce的概念。

# 我们先看map。map()函数接收两个参数，一个是函数，一个是Iterable，map将传入的函数依次作用到序列的每个元素，并把结果作为新的Iterator返回。

# 举例说明，比如我们有一个函数f(x)=x2，要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上，就可以用map()实现如下：
def f(x):
    return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(r))


# map()传入的第一个参数是f，即函数对象本身。由于结果r是一个Iterator，Iterator是惰性序列，因此通过list()函数让它把整个序列都计算出来并返回一个list。

# 你可能会想，不需要map()函数，写一个循环，也可以计算出结果：

L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
    L.append(f(n))
print(L)
# 的确可以，但是，从上面的循环代码，能一眼看明白“把f(x)作用在list的每一个元素并把结果生成一个新的list”吗？

# 所以，map()作为高阶函数，事实上它把运算规则抽象了，因此，我们不但可以计算简单的f(x)=x2，还可以计算任意复杂的函数，比如，把这个list所有数字转为字符串：
print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])))

# 只需要一行代码。

# 再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上，这个函数必须接收两个参数，reduce把结果继续和序列的下一个元素做累积计算，其效果就是：
# reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

# 比方说对一个序列求和，就可以用reduce实现：
from functools import reduce
def add(x, y):
    return x + y


print(reduce(add, [1, 3, 5, 7, 9]))

def fn(x, y):
    return x * 10 + y

print(reduce(fn, [1, 3, 5, 7, 9]))



# 这个例子本身没多大用处，但是，如果考虑到字符串str也是一个序列，对上面的例子稍加改动，配合map()，我们就可以写出把str转换为int的函数：

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}


# 整理成一个str2int的函数就是：
def str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return DIGITS[s]
    return reduce(fn, map(char2num, s))

def char2num(s):
    digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    return digits[s]

print(reduce(fn, map(char2num, '13579')))

# 还可以用lambda函数进一步简化成：

from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
    return DIGITS[s]

def str2intlambda(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))
print(str2intlambda('13579'))

# filter

# Python内建的filter()函数用于过滤序列。

# 和map()类似，filter()也接收一个函数和一个序列。和map()不同的是，filter()把传入的函数依次作用于每个元素，然后根据返回值是True还是False决定保留还是丢弃该元素。

# 例如，在一个list中，删掉偶数，只保留奇数，可以这么写：

def is_odd(n):
   return n%2==1
fliterlist=list(filter(is_odd,[1, 2, 4, 5, 6, 9, 10, 15]))
print(fliterlist)

def not_empty(s):
    r=s and s.strip()
    return r

emptylist=list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
print(emptylist)


# 可见用filter()这个高阶函数，关键在于正确实现一个“筛选”函数。

# 注意到filter()函数返回的是一个Iterator，也就是一个惰性序列，所以要强迫filter()完成计算结果，需要用list()函数获得所有结果并返回list。

# 用filter求素数
# 计算素数的一个方法是埃氏筛法，它的算法理解起来非常简单：

# 首先，列出从2开始的所有自然数，构造一个序列：

# 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

# 取序列的第一个数2，它一定是素数，然后用2把序列的2的倍数筛掉：

# 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

# 取新序列的第一个数3，它一定是素数，然后用3把序列的3的倍数筛掉：

# 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

# 取新序列的第一个数5，然后用5把序列的5的倍数筛掉：

# 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

# 不断筛下去，就可以得到所有的素数。

# 用Python来实现这个算法，可以先构造一个从3开始的奇数序列：

def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

# 注意这是一个生成器，并且是一个无限序列。

# 然后定义一个筛选函数：

def _not_divisible(n):
    return lambda x: x % n > 0

def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列

# 这个生成器先返回第一个素数2，然后，利用filter()不断产生筛选后的新的序列。

# 由于primes()也是一个无限序列，所以调用时需要设置一个退出循环的条件：

# 打印1000以内的素数:
for n in primes():
    if n < 5:
        print(n)
    else:
        break

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

# sorted

# 排序算法
# 排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序，排序的核心是比较两个元素的大小。如果是数字，我们可以直接比较，但如果是字符串或者两个dict呢？直接比较数学上的大小是没有意义的，因此，比较的过程必须通过函数抽象出来。
# 请再注意一点，当我们调用lazy_sum()时，每次调用都会返回一个新的函数，即使传入相同的参数：

# Python内置的sorted()函数就可以对list进行排序：
sortlist=sorted([36, 5, -12, 9, -21])
print(sortlist);

# 此外，sorted()函数也是一个高阶函数，它还可以接收一个key函数来实现自定义的排序，例如按绝对值大小排序：
print(sorted([36, 5, -12, 9, -21], key=abs)) 

# 我们再看一个字符串排序的例子：

print(sorted(['bob', 'about', 'Zoo', 'Credit'])) 
# ['Credit', 'Zoo', 'about', 'bob']
# 默认情况下，对字符串排序，是按照ASCII的大小比较的，由于'Z' < 'a'，结果，大写字母Z会排在小写字母a的前面。
# 现在，我们提出排序应该忽略大小写，按照字母序排序。要实现这个算法，不必对现有代码大加改动，只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串，实际上就是先把字符串都变成大写（或者都变成小写），再比较。

# 这样，我们给sorted传入key函数，即可实现忽略大小写的排序：
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower))
# 要进行反向排序，不必改动key函数，可以传入第三个参数reverse=True：
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True))


# 假设我们用一组tuple表示学生名字和成绩：

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
# 请用sorted()对上述列表分别按名字排序：

def by_name(t):
    return(t[0])
L2 = sorted(L, key=by_name)
print(L2)
print("lambda表达式按名字排序",sorted(L,key=lambda x:x[0]))
print(sorted(L,key=lambda x:x[0]))
# 再按成绩从高到低排序：

def by_score(t):
    return -t[1]
L3=sorted(L, key=by_score)
print("成绩排序:",L3)
print("lambda表达式按成绩从高到低排序:",sorted(L,key=lambda x:x[1],reverse=True))

# 返回函数
# 函数作为返回值
# 高阶函数除了可以接受函数作为参数外，还可以把函数作为结果值返回。

# 我们来实现一个可变参数的求和。通常情况下，求和的函数是这样定义的：

def calc_sum(*args):
    ax = 0
    for n in args:
        ax = ax + n
    return ax
# 但是，如果不需要立刻求和，而是在后面的代码中，根据需要再计算怎么办？可以不返回求和的结果，而是返回求和的函数：

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

f = lazy_sum(1, 3, 5, 7, 9)
print(f());
# 在这个例子中，我们在函数lazy_sum中又定义了函数sum，并且，内部函数sum可以引用外部函数lazy_sum的参数和局部变量，当lazy_sum返回函数sum时，相关参数和变量都保存在返回的函数中，这种称为“闭包（Closure）”的程序结构拥有极大的威力

f1 = lazy_sum(1, 3, 5, 7, 9)
f2 = lazy_sum(1, 3, 5, 7, 9)
print(f1==f2)


# f1()和f2()的调用结果互不影响。

# 闭包
# 注意到返回的函数在其定义内部引用了局部变量args，所以，当一个函数返回了一个函数后，其内部的局部变量还被新函数引用，所以，闭包用起来简单，实现起来可不容易。

# 另一个需要注意的问题是，返回的函数并没有立刻执行，而是直到调用了f()才执行。我们来看一个例子：

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()
print(f1());
print(f2());
print(f3());


# nonlocal
# 使用闭包，就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值，我们会发现返回的闭包函数调用一切正常：

def inc():
    x = 0
    def fn():
        # 仅读取x的值:
        return x + 1
    return fn

f = inc()
print(f()) # 1
print(f()) # 1

# 但是，如果对外层变量赋值，由于Python解释器会把x当作函数fn()的局部变量，它会报错：

# -*- coding: utf-8 -*-
# def inc():
#     x = 0
#     def fn():
#         # nonlocal x
#         x = x + 1
#         return x
#     return fn

# f = inc()
# print(f()) # 1
# print(f()) # 2

# 匿名函数
# 最后更新: 2018/5/30 14:19 / 阅读: 99203094
# 当我们在传入函数时，有些时候，不需要显式地定义函数，直接传入匿名函数更方便。

# 在Python中，对匿名函数提供了有限支持。还是以map()函数为例，计算f(x)=x2时，除了定义一个f(x)的函数外，还可以直接传入匿名函数：
lamList=list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
print(lamList)

# 通过对比可以看出，匿名函数lambda x: x * x实际上就是：
def f(x):
    return x * x

# 关键字lambda表示匿名函数，冒号前面的x表示函数参数。

# 匿名函数有个限制，就是只能有一个表达式，不用写return，返回值就是该表达式的结果。

# 用匿名函数有个好处，因为函数没有名字，不必担心函数名冲突。此外，匿名函数也是一个函数对象，也可以把匿名函数赋值给一个变量，再利用变量来调用该函数：
f = lambda x: x * x
print(f)
print(f(5))

# 同样，也可以把匿名函数作为返回值返回，比如：

def build(x, y):
    return lambda: x * x + y * y

print(build(2,3)())


# 装饰器
# 最后更新: 2022/10/9 19:50 / 阅读: 191336189
# 由于函数也是一个对象，而且函数对象可以被赋值给变量，所以，通过变量也能调用该函数。



def now():
    print('2015-3-25')
funcNow = now
funcNow()
# 函数对象有一个__name__属性（注意：是前后各两个下划线），可以拿到函数的名字：

print(now.__name__)

print(funcNow.__name__)

# 现在，假设我们要增强now()函数的功能，比如，在函数调用前后自动打印日志，但又不希望修改now()函数的定义，这种在代码运行期间动态增加功能的方式，称之为“装饰器”（Decorator）。

# 本质上，decorator就是一个返回函数的高阶函数。所以，我们要定义一个能打印日志的decorator，可以定义如下：

def log(funcNow):
    def wrapper(*args, **kw):
        print('call %s():' % funcNow.__name__)
        return funcNow(*args, **kw)
    return wrapper

# 观察上面的log，因为它是一个decorator，所以接受一个函数作为参数，并返回一个函数。我们要借助Python的@语法，把decorator置于函数的定义处：
@log
def now():
    print('2015-3-25')

now()

# 由于log()是一个decorator，返回一个函数，所以，原来的now()函数仍然存在，只是现在同名的now变量指向了新的函数，于是调用now()将执行新函数，即在log()函数中返回的wrapper()函数。

# wrapper()函数的参数定义是(*args, **kw)，因此，wrapper()函数可以接受任意参数的调用。在wrapper()函数内，首先打印日志，再紧接着调用原始函数。

# 如果decorator本身需要传入参数，那就需要编写一个返回decorator的高阶函数，写出来会更复杂。比如，要自定义log的文本：

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

# 这个3层嵌套的decorator用法如下：
@log('execute')
def now():
    print('2015-3-25')
now()

# 和两层嵌套的decorator相比，3层嵌套的效果是这样的：
print("*******和两层嵌套的decorator相比，3层嵌套的效果是这样的*******")
now = log('execute')(now)
now()
# 我们来剖析上面的语句，首先执行log('execute')，返回的是decorator函数，再调用返回的函数，参数是now函数，返回值最终是wrapper函数。

# 以上两种decorator的定义都没有问题，但还差最后一步。因为我们讲了函数也是对象，它有__name__等属性，但你去看经过decorator装饰之后的函数，它们的__name__已经从原来的'now'变成了'wrapper'：

# 因为返回的那个wrapper()函数名字就是'wrapper'，所以，需要把原始函数的__name__等属性复制到wrapper()函数中，否则，有些依赖函数签名的代码执行就会出错。

# 不需要编写wrapper.__name__ = func.__name__这样的代码，Python内置的functools.wraps就是干这个事的，所以，一个完整的decorator的写法如下：

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

# 或者针对带参数的decorator：

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

# 偏函数
# 最后更新: 2015/6/4 21:59 / 阅读: 57274143
# Python的functools模块提供了很多有用的功能，其中一个就是偏函数（Partial function）。要注意，这里的偏函数和数学意义上的偏函数不一样。

# 在介绍函数参数的时候，我们讲到，通过设定参数的默认值，可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下：

# int()函数可以把字符串转换为整数，当仅传入字符串时，int()函数默认按十进制转换：

print(int('12345'))

# 但int()函数还提供额外的base参数，默认值为10。如果传入base参数，就可以做N进制的转换：
print(int('12345', base=8))

print(int('12345', 16))

# 假设要转换大量的二进制字符串，每次都传入int(x, base=2)非常麻烦，于是，我们想到，可以定义一个int2()的函数，默认把base=2传进去：
def int2(x, base=2):
    return int(x, base)

print(int2('1000000'))
print(int2('1010101'))

# functools.partial就是帮助我们创建一个偏函数的，不需要我们自己定义int2()，可以直接使用下面的代码创建一个新的函数int2：

import functools
int2 = functools.partial(int, base=2);
print(int2('1000000'));
print(int2('1010101'));
int2('10010')

# 所以，简单总结functools.partial的作用就是，把一个函数的某些参数给固定住（也就是设置默认值），返回一个新的函数，调用这个新函数会更简单。

# 注意到上面的新的int2函数，仅仅是把base参数重新设定默认值为2，但也可以在函数调用时传入其他值：
print(int2('1000000', base=10))

# 最后，创建偏函数时，实际上可以接收函数对象、*args和**kw这3个参数，当传入：
int2 = functools.partial(int, base=2)
# 实际上固定了int()函数的关键字参数base，也就是：

print(int2('10010'))

# 相当于：
kw = { 'base': 2 }
print(int('10010', **kw))
# 当传入：

max2 = functools.partial(max, 10)
# 实际上会把10作为*args的一部分自动加到左边，也就是：
print(max2(5, 6, 7))
# 相当于：

args = (10, 5, 6, 7)
print(max(*args))